///////////////////////////////////////////////////////////////////////////
//
// Spring Engine Source File.
// Copyright (C), Dengtao 2011-2021
// ----------------------------------------------------------------------
// File name:   SfMessageDispatcher.cpp
// Version:     v1.0.0
// Created:     2013/06/16 20:32 by dengtao
// Compilers:   Visual Studio 2008
// Description: 
// ----------------------------------------------------------------------
// History:
//
///////////////////////////////////////////////////////////////////////////

#include "SfMessageDispatcher.h"
#include "SfGameObjectManager.h"
#include "SfGameObject.h"
#include <algorithm>

namespace Sunflower
{
//
//Summary:
//	Post a message without delay.
void MessageDispatcher::PostMessage(unsigned int msgId, 
	const GameObjectGUID& senderId, 
	const GameObjectGUID& recieverId, 
	MessageArgument* arg)
{
	GameObjectManager* gom = GameObjectManager::GetInstancePtr();
	SfAssertValidPtr(gom);

	GameObject* sender = gom->GetGameObjectByGoid(senderId);
	GameObject* reciever = gom->GetGameObjectByGoid(recieverId);

	PostMessage(msgId, sender, reciever, arg);
}

void MessageDispatcher::PostMessage(unsigned int msgId, 
	GameObject* sender, 
	GameObject* reciever, 
	MessageArgument* arg)
{
	Message message(msgId, sender, reciever, arg, m_LastUpdateTime);

	// if reciever is null, then broadcast
	if (SfIsNullPtr(reciever))
	{
		m_UnhandledMessageFilter(message);
	}
	else
	{
		reciever->OnMessage(message);
	}
}

//
//Summary:
//	dispatch a message without delay.
void MessageDispatcher::PostDelayedMessage(unsigned int msgId, 
	const GameObjectGUID& senderId, 
	const GameObjectGUID& recieverId, 
	MessageArgument* arg,
	float delayTime)
{
	Message message(msgId, senderId, recieverId, arg, m_LastUpdateTime);

	DelayedMessageDispatchInfo dmdi;
	dmdi.Broadcast = false;
	dmdi.DelayTime = delayTime;
	dmdi.message = message;

	m_MessageQueue.push_back(dmdi);
}

void MessageDispatcher::PostDelayedMessage(unsigned int msgId, 
	GameObject* sender, 
	GameObject* reciever, 
	MessageArgument* arg,
	float delayTime)
{
	GameObjectGUID senderId;
	GameObjectGUID recieverId;

	if (SfIsValidPtr(sender))
	{
		senderId = sender->GetGoid();
	}

	if (SfIsValidPtr(reciever))
	{
		recieverId = reciever->GetGoid();
	}

	PostDelayedMessage(msgId, senderId, recieverId, arg, delayTime);
}

//
//Summary:
//	Broadcast message to objects.
void MessageDispatcher::BroadcastMessage(const Message& message)
{
	GameObjectManager* gom = GameObjectManager::GetInstancePtr();

	if (SfIsValidPtr(gom))
	{
		GameObjectManager::IDBasedGOMap& gomap = gom->GetGoMap();

		for (GameObjectManager::IDBasedGOMapIterator it = gomap.begin();
			it != gomap.end();
			++it)
		{
			SfAssertValidPtr(it->second);

			if (SfIsValidPtr(it->second))
			{
				(it->second)->OnMessage(message);
			}
		}
	}	
}

//
//Summary:
//	Broadcast message.
void MessageDispatcher::BroadcastMessage(unsigned int msgId, 
	const GameObjectGUID& senderId,
	MessageArgument* arg)
{
	GameObjectManager* gom = GameObjectManager::GetInstancePtr();
	SfAssertValidPtr(gom);

	GameObject* sender = gom->GetGameObjectByGoid(senderId);

	BroadcastMessage(msgId, sender, arg);
}

void MessageDispatcher::BroadcastMessage(unsigned int msgId, 
	GameObject* sender,
	MessageArgument* arg)
{
	Message message(msgId, sender, SfNull, arg, m_LastUpdateTime);
	BroadcastMessage(message);
}

//
//Summary:
//	Broadcast message with delay
void MessageDispatcher::BroadcastDelayedMessage(unsigned int msgId, 
	const GameObjectGUID& senderId,
	MessageArgument* arg,
	float delayTime)
{
	Message message(msgId, senderId, GameObjectGUID::InvalidGoid, arg, 
		m_LastUpdateTime);

	DelayedMessageDispatchInfo dmdi;
	dmdi.Broadcast = true;
	dmdi.DelayTime = delayTime;
	dmdi.message = message;

	m_MessageQueue.push_back(dmdi);
}

void MessageDispatcher::BroadcastDelayedMessage(unsigned int msgId, 
	GameObject* sender,
	MessageArgument* arg,
	float delayTime)
{
	GameObjectGUID senderId;

	if (SfIsValidPtr(sender))
	{
		senderId = sender->GetGoid();
	}

	BroadcastDelayedMessage(msgId, senderId, arg, delayTime);
}

//
//Summary:
//	Update with elapsed time.
void MessageDispatcher::UpdateTime(float deltaTime)
{
	m_LastUpdateTime = m_LastUpdateTime + deltaTime;
}

//
//Summary:
//	Update all delayed message.
void MessageDispatcher::UpdateAllDelayedMessages()
{
	for (MessageQueueIterator it = m_MessageQueue.begin();
		it != m_MessageQueue.end();)
	{
		MessageQueueIterator cur_it = it;
		++it;

		DelayedMessageDispatchInfo& dmdi = *cur_it;

		// check time out
		if ((dmdi.DelayTime + dmdi.message.TimeStamp) >= m_LastUpdateTime)
		{
			// check if the message need to broadcast
			if (dmdi.Broadcast)
			{
				BroadcastMessage(dmdi.message);
			}
			else // send to a given reciever.
			{
				SfAssert(dmdi.message.Reciever.Goid.IsValidGoid());

				// check reciever object.
				bool handled = false;
				GameObjectManager* gom = GameObjectManager::GetInstancePtr();

				if (SfIsValidPtr(gom))
				{
					GameObject* reciever = 
						gom->GetGameObjectByGoid(dmdi.message.Reciever.Goid);

					if (SfIsValidPtr(reciever))
					{
						handled = true;
						reciever->OnMessage(dmdi.message);
					}
				}

				if (!handled)
				{
					m_UnhandledMessageFilter(dmdi.message);
				}
			}

			m_MessageQueue.erase(cur_it);
		} // end of check time
	} // end of for
}

//
//Summary:
//	reset.
void MessageDispatcher::Reset()
{
	m_LastUpdateTime = 0;
	m_MessageQueue.clear();
	m_UnhandledMessageFilter.Reset();
}

//
//Summary:
//	set unhandled message filter.
void MessageDispatcher::SetUnhandledMessageFilter(MessageHandleDelegate& mhd)
{
	m_UnhandledMessageFilter = mhd;
}

}
